package com.silverhaze.diamond.client.ui.event;

import com.silverhaze.diamond.client.utils.thread.BackgroundExecutor;
import com.silverhaze.diamond.client.utils.thread.BackgroundTask;
import com.silverhaze.diamond.common.constants.DiamondConstants;
import com.silverhaze.diamond.common.event.UIEvent;
import com.silverhaze.diamond.common.util.ServiceLocator;
import org.hornetq.jms.client.HornetQObjectMessage;

import javax.jms.*;
import javax.swing.*;
import java.io.Serializable;
import java.util.*;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Logger;

/**
 * User: Babanin
 * Date: 16.04.11
 * Time: 20:37
 */
public class UIClientEventDispatcher implements Runnable {
    private static UIClientEventDispatcher instance;

    private Queue<UIEvent> queue;
    private Map<Class, List<UIEventDispatcherListener>> listeners;

    private boolean eventPosted;
    private final Object lock = new Object();

    private Connection serverConnection;

    private UIClientEventDispatcher() {
        queue = new ConcurrentLinkedQueue<UIEvent>();
        listeners = new HashMap<Class, List<UIEventDispatcherListener>>();

        eventPosted = false;

        initializeServerListener();
    }

    private void initializeServerListener() {
        BackgroundExecutor.getInstance().execute(new BackgroundTask() {
            @Override
            public void execute() {
                try {
                    ConnectionFactory cf = (ConnectionFactory) ServiceLocator.lookup("/ConnectionFactory");

                    serverConnection = cf.createConnection();
                    Session session = serverConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                    Topic topic = (Topic) ServiceLocator.lookup(DiamondConstants.JMS_DATASOURCE_TOPIC);

                    MessageConsumer messageConsumer = session.createConsumer(topic);
                    messageConsumer.setMessageListener(new MessageListener() {
                        @Override
                        public void onMessage(Message message) {
                            if (message instanceof HornetQObjectMessage) {
                                HornetQObjectMessage objectMessage = (HornetQObjectMessage) message;

                                try {
                                    Serializable object = objectMessage.getObject();

                                    if (object instanceof UIEvent) {
                                        UIEvent event = (UIEvent) object;
                                        event.setServerEvent(true);

                                        Logger.getLogger(UIClientEventDispatcher.class.getName()).info(event.toString());

                                        fireEvents(event);
                                        fireEvents(new UIUpdateEntitiesEvent(event.getChangedEntityClass()));
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    });

                    serverConnection.start();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public void postEvent(UIEvent... events) {
        fireEvents(events);
    }

    private void fireEvents(UIEvent... events) {
        synchronized (lock) {
            for (UIEvent event : events) {
                if (!merge(event)) {
                    queue.addAll(Arrays.asList(events));

                    if (!eventPosted) {
                        eventPosted = true;
                        SwingUtilities.invokeLater(this);
                    }
                }
            }
        }
    }

    private boolean merge(UIEvent event) {
        if (event.isMergeable()) {
            for (UIEvent e : queue) {
                if (e.isMergeable() && e.canMergeWith(event)) {
                    e.merge(event);
                    return true;
                }
            }
        }

        return false;
    }

    private void dispatchEvents() {
        while (!queue.isEmpty()) {
            dispatchEvent(queue.remove());
        }
    }

    private void dispatchEvent(UIEvent event) {
        Class eventClass = event.getClass();
        Set<Map.Entry<Class, List<UIEventDispatcherListener>>> entries = this.listeners.entrySet();

        for (Map.Entry<Class, List<UIEventDispatcherListener>> entry : entries) {
            if (entry.getKey().isAssignableFrom(eventClass)) {
                for (UIEventDispatcherListener dispatcherListener : entry.getValue()) {
                    dispatcherListener.handleEvent(event);
                }
            }
        }
    }

    public void run() {
        synchronized (lock) {
            dispatchEvents();
            eventPosted = false;
        }
    }

    public void subscribe(UIEventDispatcherListener listener, Class... classes) {
        if (classes.length > 0) {
            for (Class clazz : classes) {
                List<UIEventDispatcherListener> classListeners = listeners.get(clazz);

                if (classListeners == null) {
                    classListeners = new ArrayList<UIEventDispatcherListener>();
                    listeners.put(clazz, classListeners);
                }

                classListeners.add(listener);
            }
        }
    }

    public void unsubscribe(UIEventDispatcherListener listener, Class... classes) {
        for (Class clazz : classes) {
            List<UIEventDispatcherListener> classListeners = listeners.get(clazz);

            if (classListeners != null) {
                classListeners.remove(listener);
            }
        }
    }

    public void unsubscribe(UIEventDispatcherListener listener) {
        for (Class clazz : listeners.keySet()) {
            List<UIEventDispatcherListener> listenerList = listeners.get(clazz);
            if (listenerList.contains(listener)) {
                listenerList.remove(listener);
            }
        }
    }

    public void closeServerConnection() {
        if (serverConnection != null) {
            try {
                serverConnection.close();
            } catch (JMSException e) {
                e.printStackTrace();
            }
        }
    }

    public static UIClientEventDispatcher getInstance() {
        if (instance == null) {
            instance = new UIClientEventDispatcher();
        }

        return instance;
    }
}